作者:辽河儿女 | 来源:互联网 | 2023-09-25 16:22
篇首语:本文由编程笔记#小编为大家整理,主要介绍了一个栈溢出C++示例相关的知识,希望对你有一定的参考价值。
本文主要演示、分析、测试函数内变量越界的问题,即栈溢出。
问题提出
很久前在测试某个工程时,发现一直能工作的模块出现了段错误。由于代码复杂,又有其它事耽搁,直到最近才集中时间调试。那个模块是循环vector,在其中计算数据再组装成字符串,最终将所有结果写到文件中。经测试发现,是在某次循环时出错。抽象化后的代码示例如下:
len = vPath.length();
for (int i = 0; i // 处理逻辑
// 处理逻辑
// 若干次调用sprintf()组装字符串
// 循环第N次出错
开始以为是处理逻辑部分出错,后发现在某次调用sprintf之后,i
的值变得十分大。超过了vector容量,因此造成段错误。
后来确认,是sprintf组装的缓冲区越界,i
的值被覆盖了。因为当时加代码片段时,没有留意缓冲区大小问题,加大容量即可解决问题。
工程代码
先给出变量的设计,如下:
int ret = 0;
int type = 10;
int id = 0;
char buffer[32] = 0;
因为本文就是模拟栈溢出情况,而栈是向下(低地址)增长的,为了让缓冲区buffer溢出覆盖其它变量,因此将其放到最后定义,其大小为32(十六进制为0x20),这样一旦溢出,就会越界波及干扰到ret
、type
、id
这几个变量,它们均为int类型,指针大小为4字节。
完整代码如下:
#include
#include
#include
// 打印buffer的内存数据
void dump(const char *buffer, int len)
int i, j, n;
int line = 16;
char c;
unsigned char* buf = (unsigned char *)buffer; // 必须是unsigned char类型
n = len / line;
if (len % line)
n++;
for (i=0; i
//printf("0x%08x: ", (unsigned int)(buf+i*line)); // linux ok
printf("0x%8p: ", buf+i*line); // windows ok
for (j=0; j
if ((i*line+j) printf("%02x ", buf[i*line+j]);
else
printf(" ");
printf(" ");
for (j=0; j
if ((i*line+j)
c = buf[i*line+j];
printf("%c", c >&#61; &#39; &#39; && c <&#39;~&#39; ? c : &#39;.&#39;);
else
printf(" ");
printf("\\n");
int main(void)
int ret &#61; 0;
int type &#61; 10;
int id &#61; 0;
char buffer[32] &#61; 0;
//dump(buffer, 48);
for (int i &#61; 0; i <16; i&#43;&#43;)
printf("---- type: %d id: %d i:%d \\n", type, id, i);
ret &#43;&#61; sprintf(buffer&#43;ret, "helloworld type: %d id: %d ", type, id);
printf("write total len: %d(0x%x)\\n", ret, ret);
printf("&#43;&#43;&#43;&#43; type: %d(0x%x) id: %d(0x%x) i: %d(0x%x)\\n", type, type, id, id, i, i);
type &#43;&#43;;
id &#43;&#43;;
printf("ptr ret: %p type: %p id: %p\\n", &ret, &type, &id);
dump((char*)(buffer), 60);
return 0;
代码比较简单&#xff0c;循环组装字符串再保存到buffer中&#xff0c;注意&#xff0c;buffer的内容是累加的——这正是溢出的根本问题。为了方便观察&#xff0c;同时打印其它变量的值及地址。本文在 x86 平台测试&#xff0c;其为小端模式&#xff0c;因为打印的值需要倒着看。
测试
一次测试结果如下&#xff1a;
---- type: 10 id: 0 i:0
write total len: 26(0x1a)
&#43;&#43;&#43;&#43; type: 10(0xa) id: 0(0x0) i: 0(0x0)
---- type: 11 id: 1 i:1
write total len: 824195711(0x31203a7f)
&#43;&#43;&#43;&#43; type: 1887007776(0x70797420) id: 1684828783(0x646c726f) i: 1684611121(0x64692031)
ptr ret: 000000000022FE48 type: 000000000022FE44 id: 000000000022FE40
0x000000000022FE20: 68 65 6c 6c 6f 77 6f 72 6c 64 20 74 79 70 65 3a helloworld type:
0x000000000022FE30: 20 31 30 20 69 64 3a 20 30 20 68 65 6c 6c 6f 77 10 id: 0 hellow
0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld!typ.: 12 id
0x000000000022FE50: 3a 20 31 20 00 00 00 00 c7 13 40 00 : 1 ......&#64;.
下面分析执行情况&#xff1a;
- 循环开始&#xff0c;第一次一切正常。
- 循环到第二次时&#xff0c;缓冲区溢出&#xff0c;
ret
、id
变量的值十分大。i
亦然&#xff0c;故循环退出&#xff0c;由于代码没有用i
作索引&#xff0c;因为没有段错误。
溢出数值分析如下&#xff1a;
-
buffer
地址为0x000000000022FE20
&#xff0c;变量id
靠近buffer
&#xff0c;其地址buffer
后的32字节偏移处&#xff0c;为000000000022FE40
&#xff0c;接着是type
&#xff0c;偏移4字节&#xff0c;地址为000000000022FE44
&#xff0c;ret
地址是000000000022FE48
。
-
id
溢出后的值是1684828783(0x646c726f)
&#xff0c;观察对应打印的二进制&#xff1a;0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld!typ.: 12 id&#xff1a;。如下&#xff1a;
0x000000000022FE40: 70 72 6c 64 ... prld
应该是hello world
最后4字节orld
&#xff0c;但有乱码。
-
type
溢出后的值是1887007776(0x70797420)
&#xff0c;观察对应打印的二进制&#xff1a;0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld !typ.: 12 id&#xff1a;。
-
ret
溢出后的值是824195711(0x31203a7f)
&#xff0c;观察对应打印的二进制&#xff1a;0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld!typ .: 12 id&#xff1a;。
综合测试分析情况&#xff0c;那些变量溢出后的数值&#xff0c;基本上就是写到buffer越界后的数据。
扩展知识
栈、堆是不同的概念——因此前面提及的仅是栈&#xff0c;理论上栈、堆都有溢出的可能。
栈溢出一般有2种可能&#xff1a;无限递归&#xff0c;变量或数组定义很大。以linux系统为例&#xff0c;栈的大小为8MiB。可用ulimit -a
查看&#xff1a;
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 253387
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
其中stack size
为8192&#xff0c;即8MiB。另外也能从中知晓文件句柄数量最大为1024(open files
字段)。
文中的“溢出”是指写到buffer数组的内容超过其容量&#xff0c;占用了其它变量的空间。至于其它的溢出情况&#xff0c;就不再深究了。
小结
本文出现的问题&#xff0c;主要原因是缓冲区容量不足引起溢出的。幸好不是生产环境的&#xff0c;否则又得急忙救火了&#xff0c;但也给自己提了个醒。编码一定要注意细节&#xff0c;内存的操作&#xff0c;数组的索引&#xff0c;指针的判断&#xff0c;等&#xff0c;都要谨慎。所谓“小心驶得万年船”&#xff0c;作为一名编码工具人&#xff0c;对代码要常怀敬畏之心。